package com.luych.toolbox.docker.service.job;

import com.luych.toolbox.common.enums.DockerContainerStatus;
import com.luych.toolbox.docker.service.constant.Constant;
import com.luych.toolbox.docker.service.dao.DockerServerDao;
import com.luych.toolbox.docker.service.dao.DockerStatDao;
import com.luych.toolbox.docker.service.entity.DockerServer;
import com.luych.toolbox.docker.service.entity.DockerStat;
import com.luych.toolbox.docker.service.sdk.DockerClient;
import com.luych.toolbox.docker.service.sdk.container.model.DockerContainerStatsResult;
import com.luych.toolbox.docker.service.sdk.container.model.DockerContainersResult;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.quartz.QuartzJobBean;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;

public class DockerServerStatJob extends QuartzJobBean {
    private static final Logger LOGGER = LoggerFactory.getLogger(DockerServerStatJob.class);

    private final DockerServerDao dockerServerDao;
    private final DockerStatDao dockerStatDao;

    public DockerServerStatJob(DockerServerDao dockerServerDao,
                               DockerStatDao dockerStatDao) {
        this.dockerServerDao = dockerServerDao;
        this.dockerStatDao = dockerStatDao;
    }

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        String id = context.getJobDetail().getJobDataMap().getString("id");

        DockerServer dockerServer = dockerServerDao.findById(id).orElseThrow(() ->
                new JobExecutionException(Constant.DOCKER_SERVER_NOT_FOUND));

        DockerClient dockerClient = new DockerClient(dockerServer.getUrl());
        DockerContainersResult dockerContainers = dockerClient.containers().all();
        dockerContainers.removeIf(container -> DockerContainerStatus.EXITED.name().equals(container.getState().toUpperCase()));

        for (DockerContainersResult.Container dockerContainer : dockerContainers) {
            try {
                DockerContainerStatsResult dockerStat = dockerClient.containers().container(dockerContainer.getId()).stats();

                Long usedMemory = dockerStat.getMemoryStats().getUsage() - dockerStat.getMemoryStats().getStats().getCache();
                Long availableMemory = dockerStat.getMemoryStats().getLimit();
                BigDecimal memoryUsage = new BigDecimal(usedMemory).multiply(new BigDecimal(100))
                        .divide(new BigDecimal(availableMemory), 2, RoundingMode.HALF_EVEN);

                Long cpuDelta = dockerStat.getCpuStats().getCpuUsage().getTotalUsage() - dockerStat.getPrecpuStats().getCpuUsage().getTotalUsage();
                Long systemCpuDelta = dockerStat.getCpuStats().getSystemCpuUsage() - dockerStat.getPrecpuStats().getSystemCpuUsage();
                BigDecimal cpuUsage = new BigDecimal(cpuDelta).multiply(new BigDecimal(100))
                        .multiply(new BigDecimal(dockerStat.getCpuStats().getOnlineCpus()))
                        .divide(new BigDecimal(systemCpuDelta), 2, RoundingMode.HALF_EVEN);

                DockerStat dockerData = new DockerStat();
                dockerData.setId(UUID.randomUUID().toString());
                dockerData.setServerId(dockerServer.getId());
                dockerData.setContainerId(dockerContainer.getId());
                dockerData.setCpuUsage(cpuUsage);
                dockerData.setMemUsage(memoryUsage);
                dockerData.setMemUsed(usedMemory);
                dockerData.setMemLimit(availableMemory);

                dockerData.setNetIn(0L);
                dockerData.setNetOut(0L);
                for (Map.Entry<String, DockerContainerStatsResult.Network> dockerNetwork : dockerStat.getNetworks().entrySet()) {
                    dockerData.setNetIn(dockerData.getNetIn() + dockerNetwork.getValue().getRxBytes());
                    dockerData.setNetOut(dockerData.getNetOut() + dockerNetwork.getValue().getTxBytes());
                }

                dockerData.setBlockIn(0L);
                dockerData.setBlockOut(0L);
                for (DockerContainerStatsResult.BlkioStat blkioStat : dockerStat.getBlkioStats().getIoServiceBytesRecursive()) {
                    if ("Read".equals(blkioStat.getOp())) {
                        dockerData.setBlockIn(dockerData.getBlockIn() + blkioStat.getValue());
                    }
                    if ("Write".equals(blkioStat.getOp())) {
                        dockerData.setBlockOut(dockerData.getBlockOut() + blkioStat.getValue());
                    }
                }
                dockerStatDao.save(dockerData);
            } catch (Exception e) {
                LOGGER.warn("docker server {} container {} stat error: {}", dockerServer.getName(), dockerContainer.getNames(), e.getMessage());
            }
        }

        List<String> containerIds = dockerContainers.stream().map(DockerContainersResult.Container::getId).collect(Collectors.toList());
        dockerStatDao.deleteByServerIdAndContainerIdNotIn(id, containerIds);
    }
}
